/*
@file: main.cpp
@author: ZZH
@time: 2022-03-09 14:34:34
@info:
*/

#include <iostream>
#include <string>
#include <fstream>
#include <ctime>
#include <cstdlib>
#include <cstring>
#include <list>
#include <getopt.h>
using namespace std;

#ifndef OUT_SUFFIX
#define OUT_SUFFIX ".bin"
#endif

#define MIN_CHAR ' '
#define MAX_CHAR '~'
#define NUM_OF_CHAR (MAX_CHAR - MIN_CHAR + 1)

/*
@brief 判断是否为ASCII可打印字符
@param c 测试字符
@return true 是可打印字符
*/
static inline bool isAsciiChar(char c) { return c <= MAX_CHAR && c >= MIN_CHAR; }

/*
@brief 判断字符串是否全部为ASCII可打印字符
@param str字符串指针
@return true 是可打印字符
*/
static inline bool isAsciiString(const char* str)
{
    while (*str)
    {
        if (not isAsciiChar(*str++))
            return false;
    }
    return true;
}

/*
@brief 判断字符串是否全部为ASCII可打印字符
@param str字符串对象引用
@return true 是可打印字符
*/
static inline bool isAsciiString(const string& str) { return isAsciiString(str.c_str()); }

//根据命令行参数设置的全局标志
bool isFileMode = false;
bool isEditOrig = false;
bool isDecryptMode = false;

//命令行参数传入的参数
string passwd;
string outFileName;
unsigned int passwdLen = 0;
list<string> inputList;

void printUseage()
{
    printf("用法: zendecry [选项] 字符串或文件......\r\n");
    printf("功能: 将给定的文件或字符串加密,可指定特定的密码,若不指定则自动生成,默认生成128位长度密码\r\n");
    printf("注意: 因为加密后的字符串有可能是空格,因此输出的加密或解密文本的前后会各放置一个'-'字符以便于区分,若加解密后的内容也有此字符,则两端最外侧的字符不属于内容\r\n");
    printf("选项说明:\r\n");

    printf("\t-p: 指定特定的密码,某些特殊字符可能会被控制台拦截,需要用双引号或单引号传递\r\n");
    printf("\t例如: -p 123456 使用123456作为密码\r\n");
    printf("\t例如: -p \"123 456\" 使用123空格456作为密码,因为空格为控制台的特殊字符,因此需要加双引号传递\r\n");

    printf("\r\n\t-n: 当不指定密码时,可使用此选项控制默认生成的密码的长度\r\n");
    printf("\t例如: -n 12 生成12位的密码\r\n");
    printf("\t注意: 当指定了-p时,此选项不起作用\r\n");

    printf("\r\n\t-f: 进入文件加密模式,选项以外的参数将被作为文件名进行使用\r\n");
    printf("\t例如: -f main.c 将main.c文件进行加密而不是将字符串main.c加密\r\n");

    printf("\r\n\t-l: 在文件加密模式下,默认不修改源文件内容而是产生新的加密文件,若指定了此选项,则会直接修改源文件内容\r\n");
    printf("\t例如: -f -l main.c 将main.c文件进行加密,且直接将加密内容写回到main.c文件内\r\n");

    printf("\r\n\t-o: 在文件加密模式下,指定输出文件的名称\r\n");
    printf("\t例如: -f -o main.enc main.c 将main.c文件进行加密,且将加密内容写到main.enc文件内\r\n");
    printf("\t注意: 若指定了-l选项,则此选项不起作用\r\n");

    printf("\r\n\t-d: 进入解密模式,将使用给定的密码对输入字符串或文件进行解密\r\n");
    printf("\t例如: -p 123456 -d \"rtvxz|\" 将会看到解密后的内容为abcdef\r\n");
    printf("\t例如: -f -p 123456 -d main.c 将使用给定的密码将main.c解密,解密后的内容输出至新文件\r\n");
    printf("\t例如: -f -l -p 123456 -d main.c 将使用给定的密码将main.c解密,解密后的内容输出至main.c\r\n");
}

const char* optString = "p:n:o:fld";

bool handleOption(int argc, char** argv)
{
    if (argc == 1)
    {
        printUseage();
        return false;
    }

    char opt = 0;
    while ((opt = getopt(argc, argv, optString)) != -1)
    {
        switch (opt)
        {
            case 'p'://设置密码
                if (passwd.empty())
                {
                    passwd = optarg;
                }
                else
                {
                    cout << "error: multiple -p" << endl;
                    return false;
                }
                break;

            case 'n'://设置密码长度
                if (passwdLen == 0)
                {
                    passwdLen = atoi(optarg);
                }
                else
                {
                    cout << "error: multiple -n" << endl;
                    return false;
                }
                break;

            case 'o'://设置输出文件
                if (outFileName.empty())
                {
                    outFileName = optarg;
                }
                else
                {
                    cout << "error: multiple -o" << endl;
                    return false;
                }
                break;

            case 'f'://文件模式
                if (isFileMode == true)
                {
                    cout << "error: multiple -f" << endl;
                    return false;
                }
                else
                {
                    isFileMode = true;
                }
                break;

            case 'l'://编辑源文件
                if (isEditOrig == true)
                {
                    cout << "error: multiple -l" << endl;
                    return false;
                }
                else
                {
                    isEditOrig = true;
                }
                break;

            case 'd'://解密模式
                if (isDecryptMode == true)
                {
                    cout << "error: multiple -d" << endl;
                    return false;
                }
                else
                {
                    isDecryptMode = true;
                }
                break;

            default:
                cout << "error: unsuport arg " << optopt << endl;
                return false;
                break;
        }

        // if (opterr != 0)
        // {
            // cout << "opt error:" << opterr << endl;
        //     return false;
        // }
    }
    return true;
}

void displayGlobalState(void)
{
/*
bool isFileMode = false;
bool isEditOrig = false;
bool isDecryptMode = false;

string passwd;
string outFileName;
unsigned int passwdLen = 0;
list<string> inputList;
*/


    cout << "isFileMode:" << isFileMode << endl;
    cout << "isEditOrig:" << isEditOrig << endl;
    cout << "isDecryptMode:" << isDecryptMode << endl;

    cout << "passwd:" << passwd << endl;
    cout << "outFileName:" << outFileName << endl;
    cout << "passwdLen:" << passwdLen << endl;

    cout << "\ninputList:" << endl;
    for (auto& input : inputList)
        cout << input << endl;
}

//使用CPU的rdrand指令产生硬件随机数
static inline size_t trand(void)
{
    size_t res;
    asm volatile("rdrand %0":"=r"(res));
    return res;
}

string generatePassword(int length = 128)
{
    length = length != 0 ? length : 128;
    char* const str = new char[length + 1];
    str[length] = '\0';

    for (int i = 0;i < length;i++)
    {
        str[i] = trand() % NUM_OF_CHAR + MIN_CHAR + 1;
    }
    string ret = str;
    delete[] str;
    return ret;
}

void en_de_buff(char *in, const char *passwd)
{
    uint32_t strLen = strlen(in);
    uint32_t pwLen = strlen(passwd);
    if (isDecryptMode)
    {
        for (uint32_t i = 0;i < strLen;i++)
        {
            if (isAsciiChar(in[i]))
            {
                in[i] = ((in[i] - MIN_CHAR + NUM_OF_CHAR - passwd[i % pwLen] + MIN_CHAR) % NUM_OF_CHAR) + MIN_CHAR;
            }
        }
    }//MASKLNSFLDFKJPQ   Thisisanexample
    else
    {
        for (uint32_t i = 0;i < strLen;i++)
        {
            if (isAsciiChar(in[i]))
            {
                in[i] = ((in[i] - MIN_CHAR + passwd[i % pwLen] - MIN_CHAR) % NUM_OF_CHAR) + MIN_CHAR;
            }
        }
    }
}

const char* en_de_crypt(const string& str, const string& passwd)
{
    uint32_t strLen = str.length();
    char* buff = new char[strLen + 1];
    memcpy(buff, str.c_str(), strLen);
    buff[strLen] = '\0';
    en_de_buff(buff, passwd.c_str());
    return buff;
}

int getFileSize(const string& str)
{
    ifstream in;
    int len = 0;
    in.open(str, ios::in | ios::binary);
    if (in.is_open())
    {
        in.seekg(0, ios::end);
        len = in.tellg();
    }
    in.close();
    return len;
}

int main(int argc, char** argv)
{
    if (not handleOption(argc, argv))
        return -1;

    //处理选项输入以外的输入参数
    for (int i = optind; i < argc;i++)
    {
        inputList.push_back(argv[i]);
    }

    // displayGlobalState();

    if (passwd.empty())
    {
        passwd = generatePassword(passwdLen);
    }
    const char* tip = isDecryptMode == true ? "解密     ":"加密";

    cout << "使用密码: " <<  passwd << " 进行" << tip << endl;

    if (isFileMode)//文件模式
    {
        for (auto& str : inputList)
        {
            int fileSize = getFileSize(str);
            if (0 != fileSize)
            {
                ifstream inFile;
                inFile.open(str, ios::in|ios::binary);
                if (inFile.is_open())
                {
                    char* buff = new char[fileSize + 1];
                    buff[fileSize] = 0;
                    inFile.read(buff, fileSize);
                    en_de_buff(buff, passwd.c_str());

                    if (outFileName.empty())
                    {
                        if (isEditOrig)
                        {
                            outFileName = str;
                        }
                        else
                        {
                            if (isDecryptMode)
                            {
                                int pos = str.rfind('.');
                                string tmpName = str.substr(0, pos) + ".txt";
                                if (tmpName == str)
                                {
                                    tmpName += ".txt";
                                }
                                outFileName = tmpName;
                            }
                            else
                            {
                                outFileName = str + ".bin";
                            }
                        }
                    }

                    ofstream outFile;
                    outFile.open(outFileName, ios::out);
                    if (outFile.is_open())
                    {
                        outFile.write(buff, fileSize);
                    }
                    else
                    {
                        cout << "无法打开输出文件:" << outFileName << endl;
                    }
                    outFile.close();
                    delete[] buff;
                }
                else
                {
                    cout << "无法打开文件:" << str << endl;
                }
                inFile.close();
            }
            else
            {
                cout << "无法打开文件,或文件为空:" << str << endl;
            }
        }
    }
    else//字符模式
    {
        for (auto& str : inputList)
        {
            const char *res = en_de_crypt(str, passwd);
            cout << '-' << res << '-' << endl;
            delete[] res;
        }
    }
    return 0;
}
